RecycleView 系列(3)-- 利用 ItemDecoration 实现时光轴(物流时间)样式类

前言

上一篇文章中详细介绍了 ItemDecoration 这个了类,了解了 RecycleView 实现分割线的原理。

下面我们来进入实战篇,首先实现一个比较常见的时光轴(物流详情)的效果。

一、 效果分析

首先我来分析一下常见的一种物流详情效果图:
1.jpg
分析可知,常规的 item 布局实现无法满足此需求,因为我们在 layout 中不知道 item 的高度为多少,中间那条竖线不容易实现。
有了上节对 ItemDecoration 的了解,我们可以将左边布局(蓝色框以左) 当做分割线来处理。

其中需要注意的有:

  1. 红色框内表示整个 item 内容
  2. 蓝色框内 item 在 布局中 固定的内容
  3. 绿色框内小图标的绘制
  4. 黄色框内时间的绘制
  5. 灰色框内竖线的绘制

二、功能实现

既然知道了思路,根据上节的步骤按部就班的就好。

1.首先模拟下数据结构,定义好 dataBean,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class LogisticsInfoBean  implements Serializable {
/**
* 物流信息
*/
private String message;
/**
* 当前物流状态
*/
private LogisticsStatus status;
/**
* 日期
*/
private String date;
/**
* 时间
*/
private String time;
}

public enum LogisticsStatus {
/**
* 一般提示语
*/
TIPS,
/**
* 已下单
*/
ORDERED,
/**
* 备货中
*/
STOCK_UP ,
/**
* 已发货
*/
DELIVERED,
/**
* 运输中
*/
TRANSPORTING,
/**
* 已收货
*/
RECEIVING
}

2.设置 getItemOffsets()

因为我们作为分割线的部分是蓝色框以左,所以我们需要设置 item 左边 left 的边距,假设预留 120 像素的边距。

1
2
3
4
5
6
7
8
9
10
11
/**
* 设置 item lef他方向的偏移量
*/
private int leftOffset = 120;

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//设置item 左边流出 leftOffset 的边距
outRect.left = leftOffset;
}
  1. 通过 onDraw() 方法绘制左边分割线内的内容

绘制是主要的部分,而 onDraw() 方法 作用于 RecycleView, 所以我们需要遍历 item ,计算绘制内容的坐标,
然后通过 canvas 的 draw 方法进行绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* 画的小圆点的半径
*/
private int circleRadius = 10;
/**
* 画的小图标的宽度
*/
private int iconWidth = 50;
private Context context;
private int padding = 24;
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
Log.d(TAG,"----onDraw---");
canvas.save();
//先画分割线整体背景色
canvas.drawColor(context.getResources().getColor(R.color.white));
final int childCount = parent.getChildCount();
//遍历
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
//1.先画 1 px 的图标上半部分的竖线
int startX = leftOffset-30;
int startY = child.getTop();
int lineStopY = startY+padding;
paint.setColor(context.getResources().getColor(R.color.gray_deep));
canvas.drawLine(startX,startY,startX,lineStopY,paint);
//2.画图形
int positon = parent.getChildAdapterPosition(child);
LogisticsInfoBean bean = dataBeanList.get(positon);
Rect dst = new Rect(startX-iconWidth/2,lineStopY,startX+iconWidth/2,lineStopY+iconWidth);
//根据不同状态画不同图形
switch (bean.getStatus()){
case TIPS:
canvas.drawCircle(startX,lineStopY+circleRadius,circleRadius,paint);
break;
case ORDERED:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_order),null,dst,null);
break;
case STOCK_UP:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_stockup),null,dst,null);
break;
case DELIVERED:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_diliver),null,dst,null);
break;
case TRANSPORTING:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_transporting),null,dst,null);
break;
case RECEIVING:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_receive),null,dst,null);
break;
default:
canvas.drawCircle(startX,lineStopY+circleRadius,circleRadius,paint);
break;
}
//画下半部分竖线
if (positon != dataBeanList.size() -1){
if (bean.getStatus() == LogisticsStatus.TIPS){
canvas.drawLine(startX,lineStopY+2*circleRadius,startX,child.getBottom(),paint);
}else {
canvas.drawLine(startX,lineStopY+iconWidth,startX,child.getBottom(),paint);
}
}
//3.画日期
canvas.drawText(bean.getDate(),startX-iconWidth/2-10,lineStopY+iconWidth/2,paint);
canvas.drawText(bean.getTime(),startX-iconWidth/2-10,lineStopY+iconWidth/2+20,paint);

}


canvas.restore();
}

4.设置数据源

让我们来造点假数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void initData() {
dataBeanList.add(new LogisticsInfoBean("[收货地址,xxxxxxx]",LogisticsStatus.RECEIVING,"02-11","10:00"));
dataBeanList.add(new LogisticsInfoBean("小主,运输中x1",LogisticsStatus.TRANSPORTING,"02-10","12:00"));
dataBeanList.add(new LogisticsInfoBean("小主,\n运输中x2",LogisticsStatus.TRANSPORTING,"02-10","12:10"));
dataBeanList.add(new LogisticsInfoBean("小主,\n\n运输中x3",LogisticsStatus.TRANSPORTING,"02-10","12:20"));
dataBeanList.add(new LogisticsInfoBean("小主,\n运输中x4",LogisticsStatus.TRANSPORTING,"02-10","12:30"));
dataBeanList.add(new LogisticsInfoBean("小主,已发货",LogisticsStatus.DELIVERED,"02-10","10:00"));
dataBeanList.add(new LogisticsInfoBean("小主,备货中",LogisticsStatus.STOCK_UP,"02-09","12:00"));
dataBeanList.add(new LogisticsInfoBean("订单支付成功,系统正在处理",LogisticsStatus.ORDERED,"02-09","10:10"));
dataBeanList.add(new LogisticsInfoBean("订单创建成功,等待支付",LogisticsStatus.TIPS,"02-09","10:00"));

adapter.notifyDataSetChanged();

}

其他一些设置这里就不在说了,然后运行看一下效果:
2.jpg
所以,实现起来也很简单有没有。

完整代码看这里:demo 传送门

总结

关于分割线的实现,不管是什么样式的,按照之前说的步骤一步步来就好:

  • 通过 getItemOffset() 方法设置 item 的偏移量**
  • onDraw()onDrawOver() 方法中完成绘制**
  • 遍历 item,计算分割线的位置**
  • 通过 draw()方法完成绘制**

其中需要注意的是各个绘制内容坐标的计算。

参考

Android 自定义View实战系列 :时间轴
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration


------------- 本文结束 感谢您的阅读 -------------